//
// Created by hjie on 23-9-24.
//

#include "stun_udp_server.h"
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <sys/socket.h>
#include "data_buffer.h"
#include "define.h"
#include "message_attr.h"
#include "stun_message.h"
#include "stun_server/stun_server.h"
#include "stun_server_manager.h"

StunUdpServer::StunUdpServer(LoopEvent *loop_event, StunServerManager * server_manager)
{
    m_loop_event = loop_event;
    m_server_manager = server_manager;
    bzero(&m_allocator_sai, sizeof(m_allocator_sai));
}

StunUdpServer::~StunUdpServer()
{
    m_udp_server->SetUdpMessage(nullptr);
    printf("release relay information nonce : %s.\n", m_connection_nonce.c_str());
}

bool StunUdpServer::CreateUdpServer()
{
    m_udp_server = std::make_shared<UdpServer>(m_loop_event);
    m_udp_server->Listen(0);

    bzero(&m_server_sai, sizeof(m_server_sai));

    socklen_t length = sizeof(m_server_sai);
    getsockname(m_udp_server->GetSocketFd(), (struct sockaddr*)&m_server_sai, &length);
    // swap public ip address
    if (!m_public_ip.empty())
    {
        inet_pton(m_server_sai.sin_family, m_public_ip.c_str(), &m_server_sai.sin_addr);
    }
    m_udp_server->SetUdpMessage(std::bind(&StunUdpServer::OnMessage, this, std::placeholders::_1));
    m_refresh_time = time(nullptr);
    return true;
}

void StunUdpServer::OnMessage(UdpPacket *packet)
{
    // check subscriber
    char buff[128] = {0};
    bzero(&buff, sizeof(buff));

    inet_ntop(packet->sai.sin_family, &packet->sai.sin_addr, buff, sizeof(buff));
    uint16_t port = ntohs(packet->sai.sin_port);

    std::string key = buff;
    key += ":" + std::to_string(port);
    if (m_subscriber_map.find(key) == m_subscriber_map.end())
    {
        // add subscriber map
        m_subscriber_map[key] = packet->sai;
        return;
    }
    // send to data to allocator address
    StunMessage message;
    message.m_msg_type = EmDataIndication;
    message.m_transaction_id = message.ProducerTransactionId();
    message.m_magic_cookie   = magic_cookie;

    auto peer_attr = new AttrXorMappedAddress();
    peer_attr->SetAttrType(EmXorPeerAddress);
    peer_attr->ParseAddrInfo(packet->sai.sin_family == AF_INET ? IPPROTO_IP : IPPROTO_IPV6, (struct sockaddr*)&packet->sai, sizeof(packet->sai));

    message.AddStunAttr(peer_attr);

    auto date_attr = new AttrData();
    date_attr->PushData(packet->data, packet->length);

    message.AddStunAttr(date_attr);
    // push to main udp server
    m_server_manager->ProcessMessage(this, message);
}

void StunUdpServer::SendDataSubscriber(AttrXorMappedAddress *address, uint8_t *data, int length)
{
    struct sockaddr_in target_sai;
    bzero(&target_sai, sizeof(target_sai));

    target_sai.sin_family = address->m_protocol == 0x01 ? AF_INET : AF_INET6;
    // target_sai.sin_family = AF_INET;
    target_sai.sin_port   = htons(address->DecoderPort());
    inet_pton(target_sai.sin_family, address->DecoderAddr().c_str(), &target_sai.sin_addr);

    UdpBufferPacket buff;
    buff.sai = (struct sockaddr*)&target_sai;
    buff.sai_len = sizeof(target_sai);

    DataBuffer buffer(m_udp_server->GetSocketFd());
    buff.buffer = &buffer;
    int send_size = m_udp_server->SendMsg(buff, data, length);

    printf("function %s [%s:%d] send data size : %d and error code : %d.\n", __func__, address->DecoderAddr().c_str(), address->DecoderPort(), send_size, errno);
}

void StunUdpServer::SendDataSubscriber(uint8_t * data, int length, const std::string & subscriber_key)
{
    UdpBufferPacket buff;
    buff.sai     = (struct sockaddr*)(&m_subscriber_map[subscriber_key]);
    buff.sai_len = sizeof(m_subscriber_map[subscriber_key]);

    DataBuffer buffer(m_udp_server->GetSocketFd());
    buff.buffer = &buffer;

    int send_size = m_udp_server->SendMsg(buff, data, length);

    printf("function %s send data size : %d and error code : %d.\n", __func__ , send_size, errno);
}

void StunUdpServer::BroadcastDataToSubscriber(uint8_t *data, int length)
{
    uint32_t packet_index = 0;
    memcpy(&packet_index, data, sizeof(packet_index));
    printf("-------------------->packet_index : %d.\n", packet_index);
    DataBuffer buffer(m_udp_server->GetSocketFd());
    for (auto & subscriber : m_subscriber_map)
    {
        std::string subscriber_key = subscriber.first;
        UdpBufferPacket buff;
        buff.buffer = &buffer;
        buff.sai     = (struct sockaddr*)(&m_subscriber_map[subscriber_key]);
        buff.sai_len = sizeof(m_subscriber_map[subscriber_key]);

        int send_size = m_udp_server->SendMsg(buff, data, length);

        printf("function %s send to [%s] data size : %d and error code : %d.\n", __func__ , subscriber_key.c_str(), send_size, errno);
    }
}

void StunUdpServer::SetPublicIp(const std::string &ip)
{
    m_public_ip = ip;
}

void StunUdpServer::SetAllocator(AttrXorMappedAddress *address)
{
    bzero(&m_allocator_sai, sizeof(m_allocator_sai));

    m_allocator_sai.sin_family = address->m_protocol == 0x01 ? AF_INET : AF_INET6;
    uint16_t port = address->LocalPort();
    m_allocator_sai.sin_port   = htons(port);
    inet_pton(m_allocator_sai.sin_family, address->LocalIp().c_str(), &m_allocator_sai.sin_addr);

}

bool StunUdpServer::AddSubscriber(AttrXorMappedAddress *address)
{
    if (!address)
    {
        printf("attr address is nullptr.\n");
        return false;
    }
    struct sockaddr_in subscriber_sai;
    bzero(&subscriber_sai, sizeof(subscriber_sai));

    subscriber_sai.sin_family = address->m_protocol == 0x01 ? AF_INET : AF_INET6;
    subscriber_sai.sin_port   = htons(address->DecoderPort());
    inet_pton(subscriber_sai.sin_family, address->DecoderAddr().c_str(), &subscriber_sai.sin_addr);

    std::string key = address->DecoderAddr() + ":" + std::to_string(address->DecoderPort());
    m_subscriber_map[key] = subscriber_sai;

    printf("function : %s key : %s.\n", __FUNCTION__, key.c_str());

    return true;
}

void StunUdpServer::UpdateAllocatorInfo(AttrXorMappedAddress * address)
{
    if (address)
    {
        struct sockaddr_in sai;
        bzero(&sai, sizeof(sai));
        
        sai.sin_port = htons(address->LocalPort());
        inet_pton(AF_INET, address->LocalIp().c_str(), &sai.sin_addr.s_addr);

        if (m_allocator_sai.sin_addr.s_addr != sai.sin_addr.s_addr)
        {
            m_allocator_sai.sin_addr.s_addr = sai.sin_addr.s_addr;
        }
        if (m_allocator_sai.sin_port != sai.sin_port)
        {
            m_allocator_sai.sin_port = sai.sin_port;
        }
    }
}